// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2022 Ivan Baidakou

#include "common.h"
#include "rotor-light.hpp"
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/wdt.h>
#include <string.h>
#include <util/delay.h>

#define LED PB5

namespace rl = rotor_light;

namespace message {

struct ChangeInterval : rl::Message {
  static constexpr auto type_id = __LINE__;
  ChangeInterval(rl::ActorId to, const rl::Duration &delta_)
      : Message{to}, delta{delta_} {}
  rl::MessageTypeId get_type_id() const override { return type_id; }
  rl::Duration delta;
};

struct Notify : rl::Message {
  static constexpr auto type_id = __LINE__;
  Notify(rl::ActorId to, const char *data_) : Message(to), data{data_} {}
  rl::MessageTypeId get_type_id() const override { return type_id; }
  const char *data;
};

} // namespace message

struct Blinker : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    next_event = 0;
    subscribe(&Blinker::on_change_interval);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    next_event = add_event(
        delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
  }

  void blink() {
    if (next_event) {
      supervisor->get_planner()->remove_event(next_event, this);
    }
    PORTB ^= (1 << LED);
    send<message::Notify>(0, notifier_id, "blink\r\n");
    next_event = add_event(
        delay, [](void *data) { static_cast<Blinker *>(data)->blink(); }, this);
  }

  void on_change_interval(message::ChangeInterval &msg) {
    if (delay > msg.delta) {
      send<message::Notify>(0, notifier_id, "dec\r\n");
    } else {
      send<message::Notify>(0, notifier_id, "inc\r\n");
    }
    delay = msg.delta;
    blink();
  }

  rl::ActorId notifier_id;
  rl::Duration delay;
  rl::TimePoint next_event;
};

struct Notifier : rl::Actor<2> {
  using Parent = Actor<2>;

  void initialize() override {
    ptr = end = nullptr;
    subscribe(&Notifier::on_notify);
    Parent::initialize();
  }

  void advance_start() override {
    Parent::advance_start();
    send<message::Notify>(0, id, "Ready\n");
  }

  void on_notify(message::Notify &message) {
    if (!ptr) { // prevent overloading
      cli();
      ptr = message.data;
      end = message.data + strlen(message.data);
      send_next();
      sei();
    }
  }

  void send_next() {
    if (end) {
      if (ptr == end) {
        ptr = nullptr;
        end = nullptr;
      } else {
        // enable empty data buffer interrupt
        UCSR0B |= (1 << UDRIE0);
        while (!(UCSR0A & (1 << UDRE0)))
          ;
        // if (!(UCSR0A & (1<<UDRE0))) {
        UDR0 = *ptr++;
        //}
      }
    }
  }

  volatile const char *ptr;
  const char *end;
};

static void app_hw_init();

using Supervisor =
    rl::Supervisor<rl::SupervisorBase::min_handlers_amount, Blinker, Notifier>;
using Storage =
    rl::traits::MessageStorage<rl::message::ChangeState,
                               rl::message::ChangeStateAck,
                               message::ChangeInterval, message::Notify>;
using Queue = rl::Queue<Storage, 5>; /* upto 5 messages in 1 queue */
using Planner = rl::Planner<2>;      /* upto 2 time events */

Queue queue;
Planner planner;
rl::Context context{&queue, &planner, &get_now};
Supervisor sup;

int main(int, char **) {
  app_hw_init();

  /* setup */
  sup.bind(context);
  auto blinker = sup.get_child<0>();
  blinker->delay = rl::Duration{1000000};
  blinker->notifier_id = sup.get_child<1>()->get_id();
  /* let it polls timer */
  sup.start(true);

  /* main cycle */
  sup.process();
  return 0;
}

static void app_hw_init() {
  // Initialize the application including WDT, PORTB.5 and TIMER0

  // We will now disable the watchdog.
  // Service the watchdog just to be sure to avoid pending timeout.
  wdt_reset();

  // Clear WDRF in MCUSR.
  MCUSR &= ~(1U << WDRF);

  // Write logical one to WDCE and WDE.
  // Keep the old prescaler setting to prevent unintentional time-out.
  WDTCSR |= (1U << WDCE) | (1U << WDE);

  // Turn off the WDT.
  WDTCSR = 0x00;

  // We will now initialize PORTB.5 to be used as an LED driver port.
  // Set PORTB.5 value to low.
  PORTB &= ~(1U << PORTB5);

  /* initializing PB5 which is connected to port 13 of uno as output*/
  DDRB |= (1 << LED);

  enable_timer();

  // USART
#define BAUD 9600
#include <util/setbaud.h>
  UBRR0H = UBRRH_VALUE;
  UBRR0L = UBRRL_VALUE;
  UCSR0B = (1 << RXEN0)    // enable rx
           | (1 << TXEN0)  // enable tx
           | (1 << RXCIE0) // enable rx interrupt
      ;

  /* Set frame format: 8data, 2stop bit */
  UCSR0C = (1 << USBS0) | (3 << UCSZ00);
  // Enable all interrupts.
  sei();
}

ISR(USART_RX_vect) {
  if (UCSR0A & (1 << RXC0)) {
    auto c = static_cast<char>(UDR0);
    auto blinker = sup.get_child<0>();
    const auto &value = blinker->delay;
    if (c == '+') {
      auto new_value = value + value / 10;
      blinker->send<message::ChangeInterval>(0, blinker->get_id(), new_value);
    } else if (c == '-') {
      auto new_value = value - value / 10;
      blinker->send<message::ChangeInterval>(0, blinker->get_id(), new_value);
    }
  }
}

ISR(USART_UDRE_vect) {
  // disable empty data buffer interrupt
  UCSR0B &= ~(1 << UDRIE0);

  auto notifier = sup.get_child<1>();
  notifier->send_next();
}
